#include "os/os.h"
#include "os_util.h"
#include "task.h"

static size_t int_flag;

int os_start(void *heap_mem, size_t heap_size)
{
    extern int os_entry(void *heap, size_t size);
    return os_entry(heap_mem, heap_size);
}

void os_int_entry(void)
{
    os_interrupt_disable();
    ++int_flag;
    os_interrupt_enable();
}

void os_int_exit(void)
{
    os_interrupt_disable();
    int_flag -= !!int_flag;
    os_interrupt_enable();
}

bool os_is_isr_context(void)
{
    if (int_flag)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void os_interrupt_disable(void)
{
    portENTER_CRITICAL();
}

void os_interrupt_enable(void)
{
    portEXIT_CRITICAL();
}

void os_scheduler_suspend(void)
{
    vTaskSuspendAll();
}

void os_scheduler_resume(void)
{
    xTaskResumeAll();
}

bool os_scheduler_is_running(void)
{
    return (bool)(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING);
}

#if (configUSE_TRACE_FACILITY == 1)
void os_sys_print_info(void)
{
#define _MSIZE_KB(N) (N) / 0x400, (N) % 0x400 * 10 / 0x400
#define _MSIZE_MB(N) (N) / 0x100000, (N) % 0x100000 * 10 / 0x100000

    TaskStatus_t *taskStatusArray;
    unsigned taskNum, i;
    char state;

    size_t heap_used;
    size_t heap_free;
    os_time_t sys_time = os_get_sys_time();
    os_heap_info(&heap_used, &heap_free, NULL);

    OS_LOG(1, "\r\n                ------====== Kernel information ======------\r\n");
    OS_LOG(1, "Run time: %d %02d:%02d:%02d:%03d (D HH:MM:SS:MS)\r\n",
           sys_time / (1000 * 60 * 60 * 24),
           sys_time % (1000 * 60 * 60 * 24) / (1000 * 60 * 60),
           sys_time % (1000 * 60 * 60) / (1000 * 60),
           sys_time % (1000 * 60) / (1000),
           sys_time % (1000) / (1));
    OS_LOG(1, "\r\nHeap:\r\n");
    // if (heap_used < 0x400)
    //     OS_LOG(1, "  Used: %u B\r\n", heap_used);
    // else if (heap_used < 0x100000)
    //     OS_LOG(1, "  Used: %u.%u KiB\r\n", _MSIZE_KB(heap_used));
    // else
    //     OS_LOG(1, "  Used: %u.%u MiB\r\n", _MSIZE_MB(heap_used));
    if (heap_free < 0x400)
        OS_LOG(1, "  Free: %u B\r\n", heap_free);
    else if (heap_free < 0x100000)
        OS_LOG(1, "  Free: %u.%u KiB\r\n", _MSIZE_KB(heap_free));
    else
        OS_LOG(1, "  Free: %u.%u MiB\r\n", _MSIZE_MB(heap_free));

    taskNum = uxTaskGetNumberOfTasks();
    taskStatusArray = os_malloc(taskNum * sizeof(TaskStatus_t));
    if (taskStatusArray == NULL)
    {
        OS_ERR("no mem\n");
        return;
    }

    i = uxTaskGetSystemState(taskStatusArray, taskNum, NULL);
    if (i != taskNum)
    {
        OS_WRN("task num %u != %u\n", i, taskNum);
    }

    OS_LOG(1, "%*sState  Pri  Idx      StkBot      StkCur  StkFreeMin\n",
           -configMAX_TASK_NAME_LEN, "Name");
    for (i = 0; i < taskNum; ++i)
    {
        OS_LOG(1, "%*.*s", -configMAX_TASK_NAME_LEN, configMAX_TASK_NAME_LEN,
               taskStatusArray[i].pcTaskName);

        switch (taskStatusArray[i].eCurrentState)
        {
        case eRunning:
            state = 'A';
            break;
        case eReady:
            state = 'R';
            break;
        case eBlocked:
            state = 'B';
            break;
        case eSuspended:
            state = 'S';
            break;
        case eDeleted:
            state = 'D';
            break;
        default:
            state = '?';
            break;
        }
        OS_LOG(1, "%-5c  %-3lu  %-3lu  %p  %p  %10u\n",
               /* State */
               state,
               /* Pri */
               taskStatusArray[i].uxCurrentPriority,
               /* Idx */
               taskStatusArray[i].xTaskNumber,
               /* StkBot */
               taskStatusArray[i].pxStackBase,
               /* StkCur */
               *(void **)taskStatusArray[i].xHandle,
               /* StkFreeMin */
               taskStatusArray[i].usStackHighWaterMark * sizeof(StackType_t));
    }
    os_free(taskStatusArray);

#undef _MSIZE_KB
#undef _MSIZE_MB
}

#else

void os_sys_print_info(void)
{
    OS_LOG(1, "os_sys_print_info() not supported, please set configUSE_TRACE_FACILITY to 1\n");
}

#endif

os_time_t os_get_sys_time(void)
{
    os_time_t ticks_count = xTaskGetTickCount();
    return OS_TicksToMSecs(ticks_count);
}

size_t os_get_sys_ticks(void)
{
    return (size_t)xTaskGetTickCount();
}

os_time_t os_calc_ticks_to_msec(size_t ticks)
{
    os_time_t msec;

    if (ticks == OS_WAIT_FOREVER)
    {
        msec = portMAX_DELAY;
    }
    else if (ticks == 0)
    {
        msec = 0;
    }
    else
    {
        msec = OS_TicksToMSecs(ticks);
        if (msec == 0)
        {
            msec = 1;
        }
    }
    return msec;
}

size_t os_calc_msec_to_ticks(os_time_t msec)
{
    size_t tick;

    if (msec == OS_WAIT_FOREVER)
    {
        tick = portMAX_DELAY;
    }
    else if (msec == 0)
    {
        tick = 0;
    }
    else
    {
        tick = OS_MSecsToTicks(msec);
        if (tick == 0)
        {
            tick = 1;
        }
    }
    return tick;
}

size_t os_cpu_usage(void)
{
    return 100;
}

#if (configUSE_POSIX_ERRNO == 1)

extern int FreeRTOS_errno;

int os_get_err(void)
{
    return FreeRTOS_errno;
}

void os_set_err(int err)
{
    FreeRTOS_errno = err;
}

#elif (configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0)

#define OS_ERRNO_LOCATION_IDX 0

int os_get_err(void)
{
    return (int)pvTaskGetThreadLocalStoragePointer(NULL, OS_ERRNO_LOCATION_IDX);
}

void os_set_err(int err)
{
    vTaskSetThreadLocalStoragePointer(NULL, OS_ERRNO_LOCATION_IDX, (void *)err);
}

#endif
